const KeepAliveImpl: ComponentOptions = {
    name: `KeepAlive`,
    __isKeepAlive: true,
    setup(props: KeepAliveProps, { slots }: SetupContext) {
        const instance = getCurrentInstance()!
        const sharedContext = instance.ctx as KeepAliveContext

        const cache: Cache = new Map()
        const keys: Keys = new Set()
        let current: VNode | null = null

        const parentSuspense = instance.suspense

        const {
            renderer: {
                p: patch,
                m: move,
                um: _unmount,
                o: { createElement },
            },
        } = sharedContext
        const storageContainer = createElement('div')

        sharedContext.activate = (vnode, container, anchor) => {
            const instance = vnode.component!
            move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
            patch(instance.vnode)
            instance.isDeactivated = false
        }

        sharedContext.deactivate = (vnode: VNode) => {
            const instance = vnode.component!
            invalidateMount(instance.m)
            invalidateMount(instance.a)

            move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
            instance.isDeactivated = true
        }

        function unmount(vnode: VNode) {
            _unmount(vnode, instance, parentSuspense, true)
        }

        function pruneCacheEntry(key: CacheKey) {
            const cached = cache.get(key) as VNode
            if (cached && (!current || !isSameVNodeType(cached, current))) {
                unmount(cached)
            }
            cache.delete(key)
            keys.delete(key)
        }

        // cache sub tree after render
        let pendingCacheKey: CacheKey | null = null
        const cacheSubtree = () => {
            cache.set(pendingCacheKey, getInnerChild(instance.subTree))
        }
        onMounted(cacheSubtree)
        onUpdated(cacheSubtree)

        onBeforeUnmount(() => {
            cache.forEach(cached => {
                unmount(cached)
            })
        })

        return () => {
            pendingCacheKey = null

            const children = slots.default()
            const rawVNode = children[0]

            let vnode = getInnerChild(rawVNode)

            const comp = vnode.type as ConcreteComponent

            const { include, exclude, max } = props

            const key = vnode.key == null ? comp : vnode.key
            const cachedVNode = cache.get(key)

            pendingCacheKey = key

            // LRU
            if (cachedVNode) {
                // copy over mounted state
                vnode.el = cachedVNode.el
                vnode.component = cachedVNode.component
                // make this key the freshest
                keys.delete(key)
                keys.add(key)
            } else {
                keys.add(key)
                // prune oldest entry
                if (max && keys.size > parseInt(max as string, 10)) {
                    pruneCacheEntry(keys.values().next().value!)
                }
            }

            current = vnode
            return vnode
        }
    },
}